/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Collision-detection class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define COLLISION_COLLISION_CC 1
#include <utility>
#include "base/module.hh"
#include "base/conf.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs.hh"
#include "math/funcs_vector.hh"
using namespace math;
#include "object/module.hh"
#include "object/dyna.hh"
#include "object/events.hh"
using namespace object;
#include "world/module.hh"
using namespace world;
#include "collision/module.hh"
#include "collision/collision.hh"
#include "collision/defs.hh"
#include "collision/events.hh"

#include <osgUtil/LineSegmentIntersector>
#include <osgUtil/IntersectionVisitor>

namespace collision {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  Collision  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

DEFINE_SINGLETON_CLASS_VARS( Collision )

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Collision::Collision( void )
:   mEnabled(true),
    mCollidables()
{
    EVENT_DESTROY_OBJECT.Listen( new DestroyObjectListener( *this ) );
}

Collision::~Collision()
{
    // Unlisten() isn't necessary for singletons.
}

/*****************************************************************************
 * Enable/disable collision-detection.
 * All other functions of the Collision singleton will remain enabled.
 *****************************************************************************/
void
Collision::Enable( const bool enable )
{
    mEnabled = enable;
}

/*****************************************************************************
 * @return True if collision-detection is enabled.
 *****************************************************************************/
bool
Collision::IfEnabled( void )
{
    return mEnabled;
}

/*****************************************************************************
 * Set an Object as collidable or indestructible.
 *****************************************************************************/
void
Collision::SetCollidable( shptr<Object> object, const bool collidable )
{
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    if ( collidable )
        mCollidables.insert( object );
    else
        mCollidables.erase( object );
}

/*****************************************************************************
 * @return Container of Objects that have collided else empty container.
 *
 * An Object could collide into multiple Objects at the same time.
 *
 * Compute intersections of 2 rays:
 * - ray of "down vector" for contact force
 * - ray of the movement of Dyna
 *****************************************************************************/
const Collision::Colliders
Collision::GetCollisions( shptr<Dyna> dyna )
{
CHECK_TYPESIG(dyna,TYPESIG_DYNA);

    // Initially no collisions.
    Collision::Colliders colliders;

    // Is collision-detection disabled?
    // Is this Dyna indestructible?
    // A special-case is to ignore zombie Dynas with a minimal graph.
    // SUBTLE: IfCollidable() will return false for "zombie" Objects
    // (which one-node "minimal graph" that will cause OSG intersection-test to fail).
    if ( (not mEnabled)
      or (not dyna->IfCollidable()) )
    {
        return colliders;
    }

    // Group of 1 or 2 LineIntersectors.
    RefPtr<osgUtil::IntersectorGroup> intersectorGroup = new osgUtil::IntersectorGroup;

    // Compute ray of "down vector" for contact force.
    const WorldVertex worldUpVector  = GET_WORLD().ComputeUpVector( dyna->GetPosition() );  // normalized
    const fp          dynaHeightHalf = dyna->GetHeight() * 0.5f;
    const WorldVertex dynaCenter = dyna->GetPosition();
    const WorldVertex dynaTop(    dynaCenter[XX] + worldUpVector[XX] * dynaHeightHalf,
                                  dynaCenter[YY] + worldUpVector[YY] * dynaHeightHalf,
                                  dynaCenter[ZZ] + worldUpVector[ZZ] * dynaHeightHalf );
    const WorldVertex dynaBottom( dynaCenter[XX] - worldUpVector[XX] * dynaHeightHalf,
                                  dynaCenter[YY] - worldUpVector[YY] * dynaHeightHalf,
                                  dynaCenter[ZZ] - worldUpVector[ZZ] * dynaHeightHalf );
    intersectorGroup->addIntersector( new osgUtil::LineSegmentIntersector( dynaTop,
                                                                           dynaBottom ) );

    // Compute ray of movement of Dyna (skip if Dyna hasn't moved far enough).
    const std::pair<WorldVertex,WorldVertex> step = dyna->GetTranslationStep();
    const WorldVertex& translationBegin           = step.first;
    const WorldVertex& translationEnd             = step.second;
    const fp           translationDistance        = Distance( translationBegin, translationEnd );
    if ( translationDistance > world::conv::Meters2Sim( collision::defs::MIN_TRANSLATION_DISTANCE_METERS ) )
    {
        intersectorGroup->addIntersector( new osgUtil::LineSegmentIntersector( translationBegin,
                                                                               translationEnd ) );
    }

    // Attach Intersectors to IntersectionVisitor.
    osgUtil::IntersectionVisitor visitor( intersectorGroup.get() );

    // For each collidable Object.
    for ( Collidables::const_iterator iter = mCollidables.begin();
          iter != mCollidables.end();
          ++iter )
    {
        // Get collidable.
        shptr<Object> collidable = *iter;
        CHECK_TYPESIG(collidable,TYPESIG_OBJECT);

        // Object should not crash into itself!
        // And ignore crashes into zombies (zombies will linger temporarily).
        if ( EX( (not collidable->IfZombie()) and (dyna.PTR() != collidable.PTR()) ) )
        {
            // Detect collision using by visiting this Object's nodes.
            collidable->GetRootNode()->accept( visitor );

            // Collision?
            if ( intersectorGroup->containsIntersections() )
            {
                // Collision occurred.
                // Append Object to reported colliders.
                colliders.push_back( collidable.PTR() );

                // Broadcast event.
                EVENT_COLLISION.Broadcast( std::make_pair( dyna.PTR(), collidable ) );
            }
        }

        // Reset intersection.
        visitor.reset();
        intersectorGroup->reset();
    }

    // Return any colliders.
    return colliders;
}

/*****************************************************************************
 * Listen for the DestroyObject event.
 *****************************************************************************/
void
Collision::DestroyObjectListener::operator()( shptr<Object> object )
{
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    GET_COLLISION().Detach( object );
}

/// (private) Directly detach an Object that is being destroyed.
void
Collision::Detach( shptr<Object> object )
{
CHECK_TYPESIG(object,TYPESIG_OBJECT);

    mCollidables.erase( object );
}

} // namespace collision
